Skip to content

feat: add mTLS client certificate support#46

Merged
phillipleblanc merged 1 commit into
trunkfrom
phillip/mtls-client-cert
May 12, 2026
Merged

feat: add mTLS client certificate support#46
phillipleblanc merged 1 commit into
trunkfrom
phillip/mtls-client-cert

Conversation

@phillipleblanc
Copy link
Copy Markdown
Contributor

Adds mTLS (mutual TLS) client certificate support, allowing the SDK to present a client certificate during the TLS handshake when connecting to a Spice runtime with client_auth_mode: request or client_auth_mode: required.

Usage

Provide the paths to a PEM-encoded client certificate and private key file when building the client. The certificate is presented to the server during the TLS handshake.

Notes

  • mTLS is an Enterprise feature of the Spice runtime.
  • Both the certificate and key file must be provided together.
  • When not set, the client behaves exactly as before (standard TLS).
  • See the mTLS cookbook recipe for a complete walkthrough.

Copilot AI review requested due to automatic review settings May 12, 2026 07:32
@phillipleblanc phillipleblanc self-assigned this May 12, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds optional mutual TLS (mTLS) client-certificate support to the Java SDK so it can present a client certificate/key during TLS negotiation when connecting to a Spice runtime.

Changes:

  • Added builder options to accept PEM client certificate and private key file paths.
  • Extended SpiceClient construction to carry the cert/key paths.
  • Configured the Arrow Flight (gRPC) TLS channel to use a client certificate via GrpcSslContexts.forClient().keyManager(...) when provided.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
src/main/java/ai/spice/SpiceClientBuilder.java Adds builder fields/setters for mTLS cert/key paths and passes them into SpiceClient.
src/main/java/ai/spice/SpiceClient.java Adds cert/key fields + constructor overload and applies the client certificate to the gRPC TLS channel.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/ai/spice/SpiceClientBuilder.java
Comment thread src/main/java/ai/spice/SpiceClientBuilder.java
Comment thread src/main/java/ai/spice/SpiceClientBuilder.java
Comment thread src/main/java/ai/spice/SpiceClient.java
Comment thread src/main/java/ai/spice/SpiceClient.java
Comment thread src/main/java/ai/spice/SpiceClient.java
@phillipleblanc phillipleblanc force-pushed the phillip/mtls-client-cert branch from b5221c1 to 283b42d Compare May 12, 2026 09:06
Copilot AI review requested due to automatic review settings May 12, 2026 12:35
@phillipleblanc phillipleblanc force-pushed the phillip/mtls-client-cert branch from 283b42d to 55d7ece Compare May 12, 2026 12:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

Comment thread src/main/java/ai/spice/SpiceClientBuilder.java
Comment thread src/main/java/ai/spice/SpiceClientBuilder.java
Comment thread src/main/java/ai/spice/SpiceClient.java
Comment thread src/main/java/ai/spice/SpiceClient.java
Comment thread src/main/java/ai/spice/SpiceClient.java
Comment thread src/main/java/ai/spice/SpiceClientBuilder.java
Comment thread src/main/java/ai/spice/SpiceClient.java
@phillipleblanc phillipleblanc force-pushed the phillip/mtls-client-cert branch from 55d7ece to 500bc3d Compare May 12, 2026 23:15
Copilot AI review requested due to automatic review settings May 12, 2026 23:48
@phillipleblanc phillipleblanc force-pushed the phillip/mtls-client-cert branch from 500bc3d to 1ba323a Compare May 12, 2026 23:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (4)

src/main/java/ai/spice/SpiceClient.java:446

  • The Javadoc block for initRetryers() is now unterminated: a new /** starts at line 443 before the previous comment is closed. This will cause a compilation error; close the first Javadoc with */ (or remove the stray line) before the buildSslContext() Javadoc begins.
    /**
     * Initializes the cached retryer instances.
    /**
     * Builds an SSLContext configured with the custom CA and/or client certificate
     * for the JDK HTTP client.
     */

src/main/java/ai/spice/SpiceClient.java:451

  • buildSslContext() introduces runtime references to BouncyCastle classes (e.g., PEMParser, JcaPEMKeyConverter, BouncyCastleProvider), but pom.xml currently has no BouncyCastle dependency. This will fail compilation for consumers; either add the required Maven dependencies (typically bcprov + bcpkix) or rework key parsing to avoid BouncyCastle.
        // Ensure BouncyCastle provider is registered for PEM private key parsing
        if (java.security.Security.getProvider("BC") == null) {
            java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        }

src/main/java/ai/spice/SpiceClient.java:462

  • buildSslContext() loads only a single certificate via CertificateFactory.generateCertificate(...). If the provided PEM file contains a certificate chain (common for client auth), the intermediate certs are dropped and the client may fail mTLS validation. Consider loading all certs (generateCertificates) and storing the full chain in the KeyStore entry to match the gRPC keyManager(certChainFile, keyFile) behavior.
            java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
            java.security.cert.Certificate clientCert;
            try (java.io.FileInputStream fis = new java.io.FileInputStream(this.tlsClientCertFile)) {
                clientCert = cf.generateCertificate(fis);
            }

src/main/java/ai/spice/SpiceClientBuilder.java:223

  • There are existing unit tests for SpiceClientBuilder (src/test/java/ai/spice/SpiceClientBuilderTest.java). Please add coverage for the new TLS options: (1) cert without key and key without cert should throw, (2) blank strings should be treated as unset (especially for tlsRootCertFile) to prevent runtime file-loading failures.
    public SpiceClient build() {
        // Validate that client cert and key are either both set or both unset
        boolean hasCert = tlsClientCertFile != null && !tlsClientCertFile.isBlank();
        boolean hasKey = tlsClientKeyFile != null && !tlsClientKeyFile.isBlank();
        if (hasCert != hasKey) {
            throw new IllegalArgumentException(
                    "Both tlsClientCertFile and tlsClientKeyFile must be provided together for mTLS. "
                    + (hasCert ? "tlsClientKeyFile is missing." : "tlsClientCertFile is missing."));
        }
        return new SpiceClient(appId, apiKey, flightAddress, httpAddress, maxRetries, userAgent, memoryLimitMB, tlsClientCertFile, tlsClientKeyFile, tlsRootCertFile);

Comment thread src/main/java/ai/spice/SpiceClientBuilder.java
Comment thread src/main/java/ai/spice/SpiceClient.java
@phillipleblanc phillipleblanc merged commit 5c5f835 into trunk May 12, 2026
36 of 38 checks passed
@phillipleblanc phillipleblanc deleted the phillip/mtls-client-cert branch May 12, 2026 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants